package org.net5ijy.oauth2.client.base;

import static org.net5ijy.commons.http.constants.StatusCode.*;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.net5ijy.commons.http.HttpClientHttp;
import org.net5ijy.commons.http.constants.HttpHeaderNames;
import org.net5ijy.commons.http.response.HtmlResponseHolder;
import org.net5ijy.commons.http.response.ResponseHolder;

/**
 * 基础版授权码模式客户端通用方法抽取
 *
 * @author XGF
 * @date 2020/8/16 15:56
 */
@Slf4j
public class BaseAuthorizationCodeMode {

  public static ResponseHolder login(
      String loginUrl, String username, String password) throws IOException {

    String authorization =
        "Basic " + Base64.encodeBase64String((username + ":" + password).getBytes());

    try (HttpClientHttp clientHttp = new HttpClientHttp()) {
      return clientHttp.html(loginUrl, new HashMap<String, String>(1) {
        {
          put(HttpHeaderNames.HEADER_NAME_AUTHORIZATION, authorization);
        }
      }, null);
    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw e;
    }
  }

  public static ResponseHolder approve(
      String sessionId, String username, String password,
      String loginUrl, String baseUrl, String authorizeUrl)
      throws IOException {

    String authorization =
        "Basic " + Base64.encodeBase64String((username + ":" + password).getBytes());

    try (HttpClientHttp clientHttp = new HttpClientHttp()) {

      // 在页面里面选择Approve然后提交
      // scope.all true
      // 携带session id cookie以便维持会话

      HtmlResponseHolder postHtml = clientHttp.postHtml(
          authorizeUrl,
          new HashMap<String, String>(2) {
            {
              put(HttpHeaderNames.HEADER_NAME_AUTHORIZATION, authorization);
              put("Cookie", "JSESSIONID=" + sessionId);
              put("Referer", loginUrl);
              put("Origin", baseUrl);
              put("Sec-Fetch-User", "?1");
              put("Sec-Fetch-Site", "same-origin");
              put("Sec-Fetch-Mode", "navigate");
              put("Upgrade-Insecure-Requests", "1");
              put("User-Agent",
                  "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
            }
          }, new HashMap<String, String>(1) {
            {
              put("user_oauth_approval", "true");
              put("scope.all", "true");
              put("authorize", "Authorize");
            }
          });

      int statusCode = postHtml.getStatusCode();

      // 不是301、302状态码
      if (statusCode != STATUS_PERMANENTLY_MOVED && statusCode != STATUS_TEMPORARILY_MOVED) {
        throw new RuntimeException("获取授权码失败");
      }

      return postHtml;

    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw e;
    }
  }

  public static String resolveCode(ResponseHolder postHtml) {

    String location = postHtml.headerValue("Location");

    System.out.println("Location: " + location);

    String group = null;
    String regex = "(\\?code=(.{6}))";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(location);
    if (matcher.find()) {
      group = matcher.group(2);
    }

    if (group == null) {
      throw new RuntimeException("解析授权码失败");
    }

    return group;
  }

  public static HtmlResponseHolder token(
      String code, String tokenUrl, String redirectUrl,
      String appId, String secret, String scope)
      throws IOException {

    try (HttpClientHttp clientHttp = new HttpClientHttp()) {

      HtmlResponseHolder htmlResponseHolder = clientHttp.postHtml(
          tokenUrl,
          new HashMap<String, String>(2) {
            {
              put(HttpHeaderNames.HEADER_NAME_AUTHORIZATION,
                  "Basic " + Base64.encodeBase64String((appId + ":" + secret).getBytes()));
            }
          }, new HashMap<String, String>(1) {
            {
              put("grant_type", "authorization_code");
              put("code", code);
              put("redirect_uri", redirectUrl);
              put("scope", scope);
            }
          });

      if (htmlResponseHolder.getStatusCode() != STATUS_OK) {
        throw new RuntimeException("获取token失败");
      }

      return htmlResponseHolder;

    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw e;
    }
  }

  public static Map refreshToken(
      String tokenUrl, String refreshToken, String appId, String secret, String scope)
      throws IOException {

    try (HttpClientHttp clientHttp = new HttpClientHttp()) {

      HtmlResponseHolder htmlResponseHolder = clientHttp.postHtml(
          tokenUrl,
          new HashMap<String, String>(2) {
            {
              put(HttpHeaderNames.HEADER_NAME_AUTHORIZATION,
                  "Basic " + Base64.encodeBase64String((appId + ":" + secret).getBytes()));
            }
          }, new HashMap<String, String>(1) {
            {
              put("grant_type", "refresh_token");
              put("refresh_token", refreshToken);
              put("scope", scope);
            }
          });

      if (htmlResponseHolder.getStatusCode() != STATUS_OK) {
        throw new RuntimeException("刷新token失败");
      }

      String content = htmlResponseHolder.getContent();
      System.out.println(content);
      ObjectMapper mapper = new ObjectMapper();

      return mapper.readValue(content, Map.class);

    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw e;
    }
  }

  public static Map getToken(AuthorizationCodeBaseEntity entity) throws IOException {

    // 第1步模拟浏览器访问，进行登录认证
    ResponseHolder responseHolder = login(entity.getLoginUrl(), entity.getUsername(),
        entity.getPassword());

    int statusCode = responseHolder.getStatusCode();

    System.out.println("login response code: " + statusCode);

    String code;

    if (statusCode == STATUS_PERMANENTLY_MOVED || statusCode == STATUS_TEMPORARILY_MOVED) {

      // 获取授权码
      code = resolveCode(responseHolder);

    } else {

      // 获取session id以便维持会话
      String jsessionid = responseHolder.cookieValue("JSESSIONID");

      // 第2步模拟选择Approve然后提交表单
      ResponseHolder postHtml =
          approve(jsessionid, entity.getUsername(), entity.getPassword(), entity.getLoginUrl(),
              entity.getBaseUrl(),
              entity.getAuthorizeUrl());

      // 会重定向到http://localhost:8080并且携带code

      // 第3步获取授权码
      code = resolveCode(postHtml);
    }

    // 打印一下code
    System.out.println("code: " + code);

    // 第4步获取token
    HtmlResponseHolder tokenResponse =
        token(
            code, entity.getTokenUrl(), entity.getRedirectUrl(), entity.getAppId(),
            entity.getSecret(), entity.getScope());

    String content = tokenResponse.getContent();
    System.out.println(content);

    ObjectMapper mapper = new ObjectMapper();
    Map value = mapper.readValue(content, Map.class);

    System.out.println("Token map: " + value);

    return value;
  }

  public static HtmlResponseHolder resourceDemo(String orderDemoUrl, String accessToken)
      throws IOException {
    try (HttpClientHttp clientHttp = new HttpClientHttp()) {
      return clientHttp.html(orderDemoUrl,
          null,
          new HashMap<String, String>(1) {
            {
              put("access_token", accessToken);
            }
          });
    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw e;
    }
  }

  @Data
  @Builder
  public static class AuthorizationCodeBaseEntity {

    private String startUrl;
    private String baseUrl;
    private String loginUrl;
    private String authorizeUrl;
    private String tokenUrl;
    private String redirectUrl;
    private String resourceDemoUrl;
    private String username;
    private String password;
    private String appId;
    private String secret;
    private String scope;
  }
}
